﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using Clandestine.Battle.Spells;
using Clandestine.Battle.Spells.Arcane;
using Clandestine.Battle.Spells.Divine;
using Clandestine.Items;
using System.Drawing;

namespace Clandestine.Battle
{
    public class Player : Combatant
    {
        private AnimatedSprite animatedSprite;
        public AnimatedSprite AnimatedSprite 
        {
            get { return animatedSprite;}
        }

        public override Sprite Sprite
        {
            get { return (Sprite)animatedSprite; }
        }

        public List<Spell> ArcaneSpells = new List<Spell>();
        public List<Spell> DivineSpells = new List<Spell>();

        private string spritesheet = string.Empty;

        public Player(string name, string spritesheet)
        {
            this.Name = name;
            this.spritesheet = spritesheet;
            this.HP = this.MaxHP;
            this.MP = this.MaxMP;

            // Tie into affliction event for sleep animation
            this.OnAffliction += delegate (Combatant c, BattleAffliction a)
            {
                if (a == BattleAffliction.Asleep)
                {
                    this.AnimatedSprite.SetAnimation("asleep");
                    this.AnimatedSprite.Interval = 500;
                    this.AnimatedSprite.Playing = true;
                }
            };
            this.OnAfflictionRemoved += delegate (Combatant c, BattleAffliction a)
            {
                if (a == BattleAffliction.Asleep)
                {
                    this.AnimatedSprite.SetAnimation("idle");
                    this.AnimatedSprite.Playing = true;
                }
            };

            // [Debug] spells
            ArcaneSpells.Add(new Fire());
            ArcaneSpells.Add(new FireMulti());
            ArcaneSpells.Add(new Poison());
            ArcaneSpells.Add(new Sleep());
            DivineSpells.Add(new Heal());
            DivineSpells.Add(new GodsWill());
        }

        public override void InitialiseSprite()
        {
            this.animatedSprite = new AnimatedSprite();
            this.AnimatedSprite.AddAnimations(spritesheet, 48, 48, new SpritesheetSelection[] {
                new SpritesheetSelection("idle", 0, 0, 0, 0),
                new SpritesheetSelection("asleep", 4, 0, 4, 2),
                new SpritesheetSelection("attack_left", 0, 3, 0, 5),
                new SpritesheetSelection("cast_spell", 2, 3, 2, 5),
                new SpritesheetSelection("walk_left", 6, 0, 6, 2),
                new SpritesheetSelection("walk_right", 7, 0, 7, 2)
            });
            this.AnimatedSprite.Visible = false;
            this.AnimatedSprite.SetAnimation("idle");
            this.AnimatedSprite.Playing = false;
        }

        public override void DeinitialiseSprite()
        {
            animatedSprite.Dispose();
        }

        public bool UseItem(BattleController battle)
        {
            // [Debug]
            List<Item> UsableItems = new List<Item>();
            UsableItems.Add(new HealthPotion());

            // -- Display menu to select an item --
            if (MessageWindow.Message == string.Empty)
                MessageWindow.SetText("Choose an item for {0} to use...", this.Name);
            Menu itemsMenu = new Menu(new Size(139, 95), Alignment.Right, new Point(0, 0), new MenuItem[] { });

            // Add the items
            List<MenuItem> menuItems = new List<MenuItem>();
            bool cancelledMenu = false;
            Item item = null;
            for (int i = 0; i < UsableItems.Count; i++)
                menuItems.Add(new MenuItem(UsableItems[i].Name, i, delegate(MenuItem m)
                {
                    item = UsableItems[(int)m.Object];
                }));
            itemsMenu.Items = menuItems.ToArray();
            itemsMenu.Position = new Point((Graphics.ScreenWidth / 2) - (itemsMenu.Width / 2), ((Graphics.ScreenHeight - 2 - itemsMenu.Height) / 2) - (itemsMenu.Height / 2));
            itemsMenu.InitializeSprites();
            itemsMenu.OnCancel += delegate
            {
                cancelledMenu = true;
            };
            itemsMenu.Enabled = true;
            itemsMenu.WaitForMenu();
            itemsMenu.Enabled = false;

            // Check they didn't cancel
            if (cancelledMenu || (item == null))
            {
                itemsMenu.Dispose();
                return false;
            }

            // -- Select target --

            // Select the target
            MessageWindow.SetText("Choose a target for {0} to use {1} on..", this.Name, item.Name);
            Combatant target = battle.SelectTarget(false, true, 0);
            MessageWindow.ClearText();

            // Did they cancel the targetting?
            if (target == null)
            {
                itemsMenu.Dispose();
                return this.UseItem(battle);
            }

            // -- Use item --
            battle.Queue.Add(delegate
            {
                // Nice messages
                MessageWindow.SetText("{0} uses {1} on {2}...", this.Name, item.Name, target.Name);

                // Walk out
                int walkAmount = 40;
                Walk(Direction.Left, walkAmount);

                // Cast spell animation
                AnimatedSprite.SetAnimation("cast_spell");
                if (target is Player)
                    Sprite.FlipHorizontal = true;
                AnimatedSprite.Playing = true;

                // Use item
                ((Potion)item).Use(target);

                // Go back
                AnimatedSprite.FlipHorizontal = false;
                Walk(Direction.Right, walkAmount);
                MessageWindow.ClearText();
            });

            itemsMenu.Dispose();
            return true;
        }

        public bool Attack(BattleController battle)
        {
            // Select an enemy
            MessageWindow.SetText("Choose a target for {0} to attack...", this.Name);
            Combatant target = battle.SelectTarget(true, true, 0);
            MessageWindow.ClearText();

            // Did they cancel the targetting?
            if (target == null)
                return false;

            // [HARDCODE DEBUG]
            int damage = 100;

            // Add the animation's action to the queue.
            battle.Queue.Add(delegate
            {
                // Obligatory target checking
                target = battle.CheckTarget(target);
                MessageWindow.SetText("{0} attacks {1}.", this.Name, target.Name);

                // Walk out
                int amount = (target is Enemy) ? 100 : 40;
                Walk(Direction.Left, amount);

                // Attack animation
                AnimatedSprite.SetAnimation("attack_left");
                if (target is Player)
                    AnimatedSprite.FlipHorizontal = true;
                AnimatedSprite.CurrentAnimation.Loop = false;
                AnimatedSprite.Playing = true;

                // Actually do damage
                target.TakeDamage(damage);

                // Walk back
                AnimatedSprite.FlipHorizontal = false;
                Walk(Direction.Right, amount);
                MessageWindow.ClearText();
            });

            return true;
        }

        public bool CastSpell(BattleController battle, SpellType type)
        {
            // Can cancel at two points: SpellMenu and Targetting,
            // for the SpellMenu, if they cancel they get to choose another action, so we need to exit this func
            // for the targetting, they can choose another spell, so we just give them that chance by calling this function again!
            bool cancelledMenu = false;

            // Get spell list now so the function is SpellType-agnostic from here on
            List<Spell> spells = (type == SpellType.Arcane) ? ArcaneSpells : DivineSpells;

            // Spell menu
            if (MessageWindow.Message == string.Empty)
                MessageWindow.SetText("Choose a spell for {0} to cast...", this.Name);
            Spell spell = new BlankSpell();
            Menu spellsMenu = new Menu(new Size(139, 95), Alignment.Right, new Point(0, 0), new MenuItem[] { });
                // Add the spells
                List<MenuItem> items = new List<MenuItem>();
                for (int i = 0; i < spells.Count; i++)
                    items.Add(new MenuItem(spells[i].Name, i, delegate (MenuItem m) {
                        spell = spells[(int)m.Object];
                    }));
                spellsMenu.Items = items.ToArray();
            spellsMenu.Position = new Point((Graphics.ScreenWidth / 2) - (spellsMenu.Width / 2), ((Graphics.ScreenHeight - 2 - spellsMenu.Height) / 2) - (spellsMenu.Height / 2));
            spellsMenu.InitializeSprites();
            spellsMenu.OnCancel += delegate {
                cancelledMenu = true;
            };
            spellsMenu.Enabled = true;
            spellsMenu.WaitForMenu();
            spellsMenu.Enabled = false;

            // Check they didn't cancel and that we have enough MP!
            if (cancelledMenu)
            {
                spellsMenu.Dispose();
                return false;
            }
            else if (this.MP < spell.MPCost)
            {
                MessageWindow.SetText("No MP to cast {0}! Choose another spell.", spell.Name);
                spellsMenu.Dispose();
                return this.CastSpell(battle, type);
            }

            // -- We now have the spell to cast in spell --
            
            // Target selection, depends on type of spell
            Combatant[] targets = null;
            bool targetEnemies = (type == SpellType.Divine) ? false : true;
            if (spell is MultipleTargetSpell)
            {
                MessageWindow.SetText("Choose a party for {0} to cast {1} on...", this.Name, spell.Name);
                targets = battle.SelectMultipleTargets(targetEnemies, true);
                MessageWindow.ClearText();
            }
            else
            {
                // Select a single
                MessageWindow.SetText("Choose a target for {0} to cast {1} on...", this.Name, spell.Name);
                targets = new Combatant[] { battle.SelectTarget(targetEnemies, true, 0) };
                MessageWindow.ClearText();
            }

            // Check they didn't cancel the targetting
            if ((targets == null) || (targets[0] == null)) // i.e. they cancelled
            {
                // Hide the menu, and call the function again (better than a loop)...
                spellsMenu.Dispose();
                return this.CastSpell(battle, type);
            }

            // Cast Spell
            CastSpellAnimation(battle, spell, targets);
           
            // Cleanup
            spellsMenu.Dispose();
            return true;
        }

        private void CastSpellAnimation(BattleController battle, Spell spell, Combatant[] targets)
        {
            // Might as well call the spellAnimation from our own delegate, no difference between
            // that and adding a beforeSpell, spellAnimation and afterSpell to the queue.
            battle.Queue.Add(delegate
            {
                // Obligatory target checking
                if (spell is MultipleTargetSpell)
                {
                    List<Combatant> newTargets = new List<Combatant>();
                    foreach (Combatant c in targets)
                        if (c.Alive)
                            newTargets.Add(c);
                    targets = newTargets.ToArray();
                }
                else
                    targets[0] = battle.CheckTarget(targets[0]);

                // Cast the spell and get the animation specific to that spell
                MessageWindow.SetText("{0} casts {1}...", this.Name, spell.Name);
                BattleAnimation spellAnimation = spell.Cast(this, targets);

                // Walk out
                int walkAmount = 40;
                Walk(Direction.Left, walkAmount);

                // Cast spell animation
                AnimatedSprite.SetAnimation("cast_spell");
                if (targets[0] is Player)
                    Sprite.FlipHorizontal = true;
                AnimatedSprite.Playing = true;

                // Spell ring
                BattleController.SpellRing.Position = Sprite.Position;
                BattleController.SpellRing.Layer = Sprite.Layer + 0.1f;
                BattleController.SpellRing.Playing = true;
                BattleController.SpellRing.Visible = true;
                spell.OnShowSpellRing();
                AnimatedSprite.Playing = false;
                BattleController.SpellRing.Visible = false;
                BattleController.SpellRing.Playing = false;

                // Spell Effects
                if (spellAnimation != null)
                    spellAnimation();

                // MP
                this.MP -= spell.MPCost;

                // Go back
                AnimatedSprite.FlipHorizontal = false;
                Walk(Direction.Right, walkAmount);
                MessageWindow.ClearText();
            });
        }

        public void Walk(Direction direction, int amount)
        {
            if (amount < 0)
                throw new ArgumentException("We don't moon walk tyvm.");

            // Set animation and get the number of pixels.
            if (direction == Direction.Right)
                this.AnimatedSprite.SetAnimation("walk_right");
            else
            {
                this.AnimatedSprite.SetAnimation("walk_left");
                amount *= -1;
            }

            // Move! 
            this.AnimatedSprite.Playing = true;
            int duration = Math.Abs(5 * amount);
            Ticker.AddTimer((ratio, deltaRatio) => this.AnimatedSprite.X += (int)(deltaRatio * amount), duration); // Blocks until complete.

            this.AnimatedSprite.SetAnimation("idle");
            this.AnimatedSprite.Playing = false;
        }
    }
}
